/*
 * Copyright 2019 Google Inc.
 *
 * Use of this source code is governed by a BSD-style license that can be
 * found in the LICENSE file.
 */

#include "modules/skottie/src/effects/Effects.h"

#include "include/effects/SkColorMatrix.h"
#include "modules/skottie/src/SkottieValue.h"
#include "modules/sksg/include/SkSGColorFilter.h"
#include "src/utils/SkJSON.h"

namespace skottie {
namespace internal {
namespace {
class InvertEffectAdapter final : public AnimatablePropertyContainer {
public:
    static sk_sp<InvertEffectAdapter> Make(const skjson::ArrayValue &jprops, sk_sp<sksg::RenderNode> layer,
        const AnimationBuilder *abuilder)
    {
        return sk_sp<InvertEffectAdapter>(new InvertEffectAdapter(jprops, std::move(layer), abuilder));
    }

    const sk_sp<sksg::ExternalColorFilter> &node() const
    {
        return fColorFilter;
    }

private:
    InvertEffectAdapter(const skjson::ArrayValue &jprops, sk_sp<sksg::RenderNode> layer,
        const AnimationBuilder *abuilder)
        : fColorFilter(sksg::ExternalColorFilter::Make(std::move(layer)))
    {
        enum : size_t {
            kChannel_Index = 0,
        };

        EffectBinder(jprops, *abuilder, this).bind(kChannel_Index, fChannel);
    }

    void onSync() override
    {
        enum class CS { kRGB, kHSL, kYIQ };

        struct STColorMatrix {
            std::array<float, 4> scale, trans;
            CS cs;
        };

        const auto stcm = [this]() -> STColorMatrix {
            // https://helpx.adobe.com/after-effects/using/channel-effects.html#invert_effect
            enum : uint8_t {
                kRGB_Channel = 1,
                kR_Channel = 2,
                kG_Channel = 3,
                kB_Channel = 4,

                // NB: HLS vs. HSL
                kHLS_Channel = 6,
                kH_Channel = 7,
                kL_Channel = 8,
                kS_Channel = 9,

                kYIQ_Channel = 11,
                kY_Channel = 12,
                kI_Channel = 13,
                kQ_Channel = 14,

                kA_Channel = 16,
            };

            switch (static_cast<uint8_t>(fChannel)) {
                case kR_Channel:
                    return { { -1, 1, 1, 1 }, { 1, 0, 0, 0 }, CS::kRGB }; // r' = 1 - r
                case kG_Channel:
                    return { { 1, -1, 1, 1 }, { 0, 1, 0, 0 }, CS::kRGB }; // g' = 1 - g
                case kB_Channel:
                    return { { 1, 1, -1, 1 }, { 0, 0, 1, 0 }, CS::kRGB }; // b' = 1 - b
                case kA_Channel:
                    return { { 1, 1, 1, -1 }, { 0, 0, 0, 1 }, CS::kRGB }; // a' = 1 - a
                case kRGB_Channel:
                    return { { -1, -1, -1, 1 }, { 1, 1, 1, 0 }, CS::kRGB };

                case kH_Channel:
                    return { { -1, 1, 1, 1 }, { .5f, 0, 0, 0 }, CS::kHSL }; // h' = .5 - h
                case kS_Channel:
                    return { { 1, -1, 1, 1 }, { 0, 1, 0, 0 }, CS::kHSL }; // s' = 1 - s
                case kL_Channel:
                    return { { 1, 1, -1, 1 }, { 0, 0, 1, 0 }, CS::kHSL }; // l' = 1 - l
                case kHLS_Channel:
                    return { { -1, -1, -1, 1 }, { .5f, 1, 1, 0 }, CS::kHSL };

                case kY_Channel:
                    return { { -1, 1, 1, 1 }, { 1, 0, 0, 0 }, CS::kYIQ }; // y' = 1 - y
                case kI_Channel:
                    return { { 1, -1, 1, 1 }, { 0, 0, 0, 0 }, CS::kYIQ }; // i' = -i
                case kQ_Channel:
                    return { { 1, 1, -1, 1 }, { 0, 0, 0, 0 }, CS::kYIQ }; // q' = -q
                case kYIQ_Channel:
                    return { { -1, -1, -1, 1 }, { 1, 0, 0, 0 }, CS::kYIQ };

                default:
                    return { { 1, 1, 1, 1 }, { 0, 0, 0, 0 }, CS::kRGB };
            }

            SkUNREACHABLE;
        }();

        SkColorMatrix m(stcm.scale[0], 0, 0, 0, stcm.trans[0], 0, stcm.scale[1], 0, 0, stcm.trans[1], 0, 0,
            stcm.scale[2], 0, stcm.trans[2], 0, 0, 0, stcm.scale[3], stcm.trans[3]

        );

        if (stcm.cs == CS::kYIQ) {
            // https://en.wikipedia.org/wiki/YIQ
            static constexpr SkColorMatrix RGB2YIQ(0.2990f, 0.5870f, 0.1140f, 0, 0, 0.5959f, -0.2746f, -0.3213f, 0, 0,
                0.2115f, -0.5227f, 0.3112f, 0, 0, 0, 0, 0, 1, 0);
            static constexpr SkColorMatrix YIQ2RGB(1, 0.9560f, 0.6190f, 0, 0, 1, -0.2720f, -0.6470f, 0, 0, 1, -1.1060f,
                1.7030f, 0, 0, 0, 0, 0, 1, 0);

            m.preConcat(RGB2YIQ);
            m.postConcat(YIQ2RGB);
        }

        fColorFilter->setColorFilter(stcm.cs == CS::kHSL ? SkColorFilters::HSLAMatrix(m) : SkColorFilters::Matrix(m));
    }

    const sk_sp<sksg::ExternalColorFilter> fColorFilter;

    float fChannel = 0;
};
} // namespace

sk_sp<sksg::RenderNode> EffectBuilder::attachInvertEffect(const skjson::ArrayValue &jprops,
    sk_sp<sksg::RenderNode> layer) const
{
    return fBuilder->attachDiscardableAdapter<InvertEffectAdapter>(jprops, std::move(layer), fBuilder);
}
} // namespace internal
} // namespace skottie
